#define MSIGMP_C
#include <stdlib.h> 
#include <stdio.h>
#include <string.h> 
#include <unistd.h>
#include <libmscommon/mscommon.h>
#include <libmscommon/msstring.h>
#include <libmscommon/mstime.h>
#include <libmscommon/msnetwork.h>
#include <libmscommon/msnetframe.h>
#include <libmscommon/mssysctl.h>
#include <libmslog/mslog.h>
#include "igmp.h"

#define FLAG "IGMP"
#define IGMP_DBG_ENABLE_PPPOE	ms_false
static IGMPContext igmp_ctt;
static ms_bool igmp_innerapi_isPppoe(UDPOpt  *pudp_opt)
{
	return (ms_null!=pudp_opt->ppp_pyIfcIn) ? ms_true : ms_false;
}
static ms_s32 igmp_innerapi_enableManual(UDPOpt  *pudp_opt)
{
	IGMPContext *pigmp_ctt=&igmp_ctt;
	if(ms_true==pigmp_ctt->igmp_enable_manual){
		if(ms_true==igmp_innerapi_isPppoe(pudp_opt)){
			return IGMP_DBG_ENABLE_PPPOE;
		}else{
			return ms_true;
		}
	}else{
		return ms_false;
	}
}
static ms_s32 igmp_innerapi_matchEthn(NETContext *pnet_ctt,UDPOpt  udp_opt)
{
	ms_pamcheckRet(-1, udp_opt.ifcIn, "pinterface");
	ms_pamcheckRet(-1, udp_opt.ipIn, "plocalhostipin");
	ms_s32 index=0;

	//查找匹配的网卡是否已经在列表中
	for( index=0;index<pnet_ctt->num;index++){
		//找到网卡
		if(ms_strncmp_saeq(udp_opt.ifcIn, pnet_ctt->netcfg_info[index].name)){
			//如果IP地址已经发生变化，那么更新IP地址。如：修改了IP地址，重新拨号了
			if(ms_strncmp_snaeq(udp_opt.ipIn, pnet_ctt->netcfg_info[index].ipaddress)){
				ms_strcpy(pnet_ctt->netcfg_info[index].ipaddress, udp_opt.ipIn);
			}
			//PPPOE网卡：可能指向的网卡发生改变，因此重新获取MAC地址
			if(ms_true==igmp_innerapi_isPppoe(&udp_opt)){
				msnet_api_getIfaceMac_string(udp_opt.ppp_pyIfcIn, pnet_ctt->netcfg_info[pnet_ctt->num].address, msnet_maxlen_mac);
			}
			return index;
		}
	}

	//如果未查找到：可能是PPPOE网卡，或者临时增加的网卡
	if(pnet_ctt->num<msnet_maxnum_iface){
		index=pnet_ctt->num;
		ms_strcpy(pnet_ctt->netcfg_info[index].name, udp_opt.ifcIn);	
		ms_strcpy(pnet_ctt->netcfg_info[index].ipaddress, udp_opt.ipIn);
		//PPPOE网卡，MAC使用指向的物理网卡地址
		if(ms_true==igmp_innerapi_isPppoe(&udp_opt)){
			msnet_api_getIfaceMac_string(udp_opt.ppp_pyIfcIn, pnet_ctt->netcfg_info[index].address, msnet_maxlen_mac);
			pnet_ctt->rtpraw_urlctt[index].ifcOut=udp_opt.ppp_pyIfcIn;
		}else{
			msnet_api_getIfaceMac_string(udp_opt.ifcIn, pnet_ctt->netcfg_info[index].address, msnet_maxlen_mac);
			pnet_ctt->rtpraw_urlctt[index].ifcOut=udp_opt.ifcIn;
		}
		//为PPPOE网卡打开上下文
		ms_info("ifcIn:%s",udp_opt.ifcIn)
		pnet_ctt->rtpraw_urlctt[index].flags=FLAG_WRITE;
		pnet_ctt->pffurl_raw->url_open(&pnet_ctt->rtpraw_urlctt[index]);
		
		pnet_ctt->num+=1;
		return index;
	}
	return -1;
}
static char msigmp_innerapi_getNetinfo(NETContext *pnet_ctt)
{
#if defined(OS_LINUX_SOC)
	ms_bufcheckRet(-1, pnet_ctt);

	FILE *msfp_netdev = fopen("/proc/net/dev", "r");
	if(ms_null==msfp_netdev){
		ms_errRet(-1,"Open /proc/net/dev failed");
	}
	char s[512]={0};
	ms_string delim = " \t\n:";
	ms_string token  = ms_null;
	ms_u08 eth_index=0;
	fseek( msfp_netdev, 0, SEEK_SET );
	pnet_ctt->num=0;
	while (fgets(s, 512, msfp_netdev)) {
		 token  = strtok(s, delim);
		ms_enDbg(0,"------------%s,%s",token,s);
		if( ms_strncmp_saeq(token,"Inter-|") ||ms_strncmp_saeq(token,"face")||ms_strncmp_saeq(token,"lo")||ms_strncmp_seq(token,"virbr")){
			continue;
		}
		if(ms_strncmp_seq(token,"ppp")){
			continue;
		}
		NETCFGInfo *pnetcfg_info=(NETCFGInfo *)&pnet_ctt->netcfg_info[pnet_ctt->num];
		if(ms_strncmp_seq(token,"wlp")){
			pnetcfg_info->flag_wifi=ms_true;
		}
//Get name	
		//pnet_ctt->cfg_info[pnet_ctt->num].id=0x88888888+eth_index;
		ms_strcpy(pnetcfg_info->name,token);
//Get mac	
		msnet_api_getIfaceMac_string(pnetcfg_info->name, pnetcfg_info->address, sizeof(pnetcfg_info->address));
//Get mtu	
		msnet_api_getIfaceMtu(pnetcfg_info->name, pnetcfg_info->mtu, sizeof(pnetcfg_info->mtu));
//Get ipaddr,netmask				
		 msnet_api_getIfaceInfo(pnetcfg_info);
//Get num	 
		 pnet_ctt->num+=1;
		if(pnet_ctt->num>msnet_maxnum_iface){
			ms_waring("Only support %d num_iface,force set num_iface from %d to %d", msnet_maxnum_iface,pnet_ctt->num, msnet_maxnum_iface);
			pnet_ctt->num=msnet_maxnum_iface;
			goto ms_end;
		}	
	}
#else
	ms_funcNosupports;
#endif
ms_end:
	fclose(msfp_netdev);
	return 0;
}
ms_bool igmp_innerapi_send(IGMPContext *pigmp_ctt,IGMPLISTContext *pigmplist_ctt,ENUIGMPCmd igmp_cmd)
{
	MSBULDIGMPContext igmp_ctt_in;
	NETCFGInfo *pnetcfg_info=&pigmp_ctt->net_ctt.netcfg_info[pigmplist_ctt->index_interface];
	ms_stru0(&igmp_ctt_in, MSBULDIGMPContext);
	igmp_ctt_in.str_source_mac	=pnetcfg_info->address;
	igmp_ctt_in.source_ip		=pnetcfg_info->ipaddress;
	igmp_ctt_in.igmp_ver		=pigmp_ctt->igmp_ver;
	igmp_ctt_in.igmp_cmd		=igmp_cmd;				//0-query;1-report;2-leave_group
	igmp_ctt_in.str_dest_mac	=pigmplist_ctt->dest_mac;	//"01:00:5e:02:02:02";
	igmp_ctt_in.dest_ip			=pigmplist_ctt->pmaddr;
	igmp_ctt_in.flag_include	=pigmplist_ctt->flag_include;
	igmp_ctt_in.num_src		=pigmplist_ctt->num_src;
	ms_memcpy(igmp_ctt_in.source_addr, pigmplist_ctt->source_addr, ms_bufsize(pigmplist_ctt->source_addr));
	ms_byte outbuf_igmp[2048]={0};
	ETHERContext ether_ctt_igmp;
	ms_stru0(&ether_ctt_igmp, ETHERContext);
	ms_s32 len=msframe_api_buildIgmp(&ether_ctt_igmp,&igmp_ctt_in,pigmplist_ctt->flag_pppoe,outbuf_igmp);
	ms_enBufHex(IGMP_DBG_ENABLE_PPPOE,"len", outbuf_igmp, len);
	if(pigmplist_ctt->flag_pppoe){
		if(ms_false==pigmplist_ctt->flag_getInfo){
			pigmp_ctt->net_ctt.pffurl_raw->url_write(&pigmp_ctt->net_ctt.rtpraw_urlctt[pigmplist_ctt->index_interface],outbuf_igmp,len);
		}else{
			pigmp_ctt->net_ctt.pffurl_raw->url_read(&pigmp_ctt->net_ctt.rtpraw_urlctt[pigmplist_ctt->index_interface],outbuf_igmp,len);
		}
	}else{
		pigmp_ctt->net_ctt.pffurl_raw->url_write(&pigmp_ctt->net_ctt.rtpraw_urlctt[pigmplist_ctt->index_interface],outbuf_igmp,len);
	}
}

ms_bool igmp_innerapi_listadd(IGMPContext *pigmp_ctt,ms_string pmaddr,UDPOpt  udp_opt)
{
	ms_bool ret=ms_true;
	ms_s32 index=0;
	ms_s32 index_canuse=-1;
	ms_s32 index_interface=igmp_innerapi_matchEthn(&pigmp_ctt->net_ctt, udp_opt);
	if(index_interface<0){
		ms_waring("Cannot get  interface(%s) info", udp_opt.ifcIn);
		return ms_false;
	}

mslock_api_do(&pigmp_ctt->mslock_ctt);
	//尝试在已有列表中查询，并记录没有被使用的列表项
	for( index=0;index<igmp_max_num;index++){
		IGMPLISTContext *pigmplist_ctt=&pigmp_ctt->igmplist_ctt[index];
		if(pigmplist_ctt->num_report>0){
			//同一个网卡，而且组播地址相同
			if((index_interface==pigmplist_ctt->index_interface)&ms_strncmp_saeq(pmaddr, pigmplist_ctt->pmaddr)){
				pigmplist_ctt->num_report+=1;
				ms_debug("Find   interface(%d) and maddr (%s), num_report=%d", pigmplist_ctt->index_interface,pigmplist_ctt->pmaddr,pigmplist_ctt->num_report);
				break;
			}
		}else{
			if(-1==index_canuse){
				index_canuse=index;
			}
		}
	}

	//没有找到
	if(!(index<igmp_max_num)){
		ms_info("No find   interface(%s) and maddr (%s), record it to %d", udp_opt.ifcIn,pmaddr,index_canuse);
		if(-1==index_canuse){
			ret=ms_false;
			ms_warGoto(ms_end, "No vaild igmp-list");
		}else{
			//分配新的列表
			IGMPLISTContext *pigmplist_ctt=&pigmp_ctt->igmplist_ctt[index_canuse];
			ms_strcpy(pigmplist_ctt->pmaddr, pmaddr);
			pigmplist_ctt->num_report=1;
			pigmplist_ctt->index_interface=index_interface;
			pigmplist_ctt->flag_pppoe=igmp_innerapi_isPppoe(&udp_opt);
			//Get dest_mac
			if(pigmplist_ctt->flag_pppoe){
				ms_strcpy(pigmplist_ctt->dest_mac, "b0:83:fe:da:39:f1" );
			}else{
				msnet_api_getMulticastDestMac(pigmplist_ctt->dest_mac, pmaddr);
			}
			//发送加入报文
			igmp_innerapi_send(pigmp_ctt, pigmplist_ctt, IGMP_CMD_REPORT);
		}
	}
ms_end:
mslock_api_undo(&pigmp_ctt->mslock_ctt);
	return ret;
}
ms_s08 igmp_innerapi_listdel(IGMPContext *pigmp_ctt,ms_string pmaddr,UDPOpt  udp_opt)
{
	ms_bool ret=ms_false;
	ms_s32 index=0;
	ms_s32 index_interface=igmp_innerapi_matchEthn(&pigmp_ctt->net_ctt, udp_opt);
	if(index_interface<0){
		ms_waring("Cannot get  interface(%s) info", udp_opt.ifcIn);
		return ms_false;
	}
mslock_api_do(&pigmp_ctt->mslock_ctt);
	for( index=0;index<igmp_max_num;index++){
		IGMPLISTContext *pigmplist_ctt=&pigmp_ctt->igmplist_ctt[index];
		if(pigmplist_ctt->num_report>0){
			if((index_interface==pigmplist_ctt->index_interface)
				&ms_strncmp_saeq(pmaddr, pigmplist_ctt->pmaddr)){
				pigmplist_ctt->num_report-=1;
				//发送离开报文
				igmp_innerapi_send(pigmp_ctt, pigmplist_ctt, IGMP_CMD_LEAVE_GROUP);
				ret=ms_true;
				goto ms_end;
			}
		}
	}
ms_end:
mslock_api_undo(&pigmp_ctt->mslock_ctt);
	return ret;
}
ms_void * igmp_innerapi_done(ms_void * param)
{
	msthread_api_setName("igmpMtor");
	IGMPContext *pigmp_ctt=(IGMPContext *)param;
	ms_s32 index_list=0;
	ms_s32 index_send=0;
	time_t bash_timep=0;
	while(ms_true){
		//按设定间隔时间发送IGMP数据，由于是统一发送可能存在小的时间误差
		if(mstime_api_counterAsyncSec(&bash_timep,pigmp_ctt->igmp_report_time)){
			//扫描list里面是否有需要发送IGMP报文
			for( index_list=0;index_list<igmp_max_num;index_list++){
				IGMPLISTContext *pigmplist_ctt=&pigmp_ctt->igmplist_ctt[index_list];
				if(pigmplist_ctt->num_report>0){
					//ms_debug("[%d]num_report:%d", index_list,pigmplist_ctt->num_report);
					//发送报文
					for( index_send=0;index_send<pigmplist_ctt->num_report;index_send++){
						igmp_innerapi_send(pigmp_ctt, pigmplist_ctt, IGMP_CMD_REPORT);
					}
				}
			}
		}
		if(ms_true==pigmp_ctt->flag_stop){
			goto ms_end;
		}
		ms_msleep(100);
	}
ms_end:
	pigmp_ctt->flag_stop=ms_false;
}
ms_bool igmp_innerapi_check_param(URLContext *s)
{
	UDPOpt  *pudp_opt=&s->opt.udp;
	if((ms_null==pudp_opt->ifcIn)
		||(ms_null==pudp_opt->ipIn)
		||(0==ms_buflen(pudp_opt->ifcIn))
		||(0==ms_buflen(pudp_opt->ipIn))
	){
		return ms_false;
	}else{
		return ms_true;
	}
}
ms_s08 igmp_api_joinGroupSources(URLContext *s,ms_bool is_include)
{
	if(ms_false==igmp_innerapi_check_param( s)){
		ms_errRet(-1, "Error interfacein  or localhostipin");
	}
	UDPOpt  *pudp_opt=&s->opt.udp;

	ms_debug("[IN]Join interface,localhostip:%s-%s (%s)",pudp_opt->ifcIn,pudp_opt->ipIn, s->url);
	mssysctl_api_setEth_rpfilter(pudp_opt->ifcIn, pudp_opt->enable_rpfilter);

	IGMPContext *pigmp_ctt=&igmp_ctt;
	if(ms_true==igmp_innerapi_enableManual(pudp_opt)){
		ms_waring("ManlIgmp No support groupsources");
	}

	struct ip_mreq_source mreqs;
	memset(&mreqs, 0, sizeof(struct ip_mreq_source) );
	mreqs.imr_multiaddr.s_addr =inet_addr(s->url_info.hostname);
	mreqs.imr_interface.s_addr= ( ms_null==pudp_opt->ipIn) ? INADDR_ANY : inet_addr(pudp_opt->ipIn);

	ms_u08 nb_sources= (ms_true==is_include) ? pudp_opt->num_include_sources : pudp_opt->num_exclude_sources;
	ms_u08 i;
	for (i = 0; i < nb_sources; i++) {
		ms_string ipaddr= (ms_true==is_include) ? pudp_opt->include_sources[i] : pudp_opt->exclude_sources[i];
		mreqs.imr_sourceaddr.s_addr = inet_addr(ipaddr) ;
		if (setsockopt(s->fd, IPPROTO_IP,(ms_true==is_include) ? IP_ADD_SOURCE_MEMBERSHIP : IP_BLOCK_SOURCE,(const void *)&mreqs, sizeof(mreqs)) < 0) {
			if (ms_true==is_include){
				ms_errRet(-1, "setsockopt(IP_ADD_SOURCE_MEMBERSHIP) %s failed,(%d,%s)", ipaddr,errno,strerror(errno));
			}else{
				ms_errRet(-1, "setsockopt(IP_BLOCK_SOURCE)  %s failed,(%d,%s)",ipaddr,errno,strerror(errno));
			}
		}
	}
	return 0;
}
int igmp_api_join(URLContext *s)
{	
	if(ms_false==igmp_innerapi_check_param( s)){
		ms_errRet(-1, "Error interfacein  or localhostipin");
	}
	UDPOpt  *pudp_opt=&s->opt.udp;
	ms_debug("[IN]Join interface,localhostip:%s-%s (%s)",pudp_opt->ifcIn,pudp_opt->ipIn, s->url);
	mssysctl_api_setEth_rpfilter(pudp_opt->ifcIn, pudp_opt->enable_rpfilter);
	IGMPContext *pigmp_ctt=&igmp_ctt;
	if(ms_true==igmp_innerapi_enableManual(pudp_opt)){
		igmp_innerapi_listadd(pigmp_ctt, s->url_info.hostname, s->opt.udp);
	}
	
	struct ip_mreq mreq;
	memset(&mreq, 0, sizeof(struct ip_mreq) );
	mreq.imr_multiaddr.s_addr = inet_addr(s->url_info.hostname);
	mreq.imr_interface.s_addr= ( ms_null==pudp_opt->ipIn) ? INADDR_ANY : inet_addr(pudp_opt->ipIn);
	if (setsockopt(s->fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, (const void *)&mreq, sizeof(mreq)) < 0) {
		ms_errRet(-1, "setsockopt(IP_ADD_MEMBERSHIP) %s (%d,%s)", s->opt.udp.ipIn,errno,strerror(errno) );
	}
	return 0;
}
int igmp_api_leave(URLContext *s)
{
	if(ms_false==igmp_innerapi_check_param( s)){
		ms_errRet(-1, "Error interfacein  or localhostipin");
	}
	UDPOpt  *pudp_opt=&s->opt.udp;
	ms_debug("[IN]Leave interface,localhostip:%s-%s(%s)",pudp_opt->ifcIn,pudp_opt->ipIn, s->url);

	IGMPContext *pigmp_ctt=&igmp_ctt;
	if(ms_true==igmp_innerapi_enableManual(pudp_opt)){
		igmp_innerapi_listdel(pigmp_ctt, s->url_info.hostname, s->opt.udp);
	}
	
	struct ip_mreq mreq;
	memset(&mreq, 0, sizeof(struct ip_mreq) );
	mreq.imr_multiaddr.s_addr = inet_addr(s->url_info.hostname);
	mreq.imr_interface.s_addr= ( ms_null==pudp_opt->ipIn) ? INADDR_ANY : inet_addr(pudp_opt->ipIn);
	if (setsockopt(s->fd, IPPROTO_IP, IP_DROP_MEMBERSHIP, (const void *)&mreq, sizeof(mreq)) < 0) {
		ms_errRet(-1, "setsockopt(IP_DROP_MEMBERSHIP) %s (%d,%s)", s->opt.udp.ipIn,errno,strerror(errno) );
	}
	return 0;
}
ms_s32 igmp_api_getVersion(ms_void)
{
	return mssysctl_api_getIgmpVersion();
}
ms_bool igmp_api_init(ms_bool igmp_enable_manual,ms_u08 igmp_ver,ms_u32 igmp_report_time)
{
	IGMPContext *pigmp_ctt=&igmp_ctt;
	ms_stru0(pigmp_ctt, IGMPContext);

	//获取参数设置：是否启用程序辅助发送IGMP，IGMP版本，发送间隔时间
	pigmp_ctt->igmp_enable_manual=igmp_enable_manual;
	pigmp_ctt->igmp_ver=igmp_ver;
	pigmp_ctt->igmp_report_time=igmp_report_time;

	//设置IGMP版本：v1,v2,v3
	mssysctl_api_setIgmpVersion(igmp_ver);

	//如果启用程序辅助发送IGMP
	if(ms_true==pigmp_ctt->igmp_enable_manual){
		//获取网卡信息，包括MAC和IP地址
		msigmp_innerapi_getNetinfo(&pigmp_ctt->net_ctt);
		//获取原始套接字协议句柄
		pigmp_ctt->net_ctt.pffurl_raw=(URLProtocol *)msptc_api_matchByPtcindex(msptc_raw);

		//初始化发送相关：统一打开所有网卡的原始套接协议
		ms_s32 index=0;
		for( index=0;index<pigmp_ctt->net_ctt.num;index++){
			pigmp_ctt->net_ctt.rtpraw_urlctt[index].ifcOut=pigmp_ctt->net_ctt.netcfg_info[index].name;
			pigmp_ctt->net_ctt.rtpraw_urlctt[index].flags=FLAG_WRITE;
			pigmp_ctt->net_ctt.pffurl_raw->url_open(&pigmp_ctt->net_ctt.rtpraw_urlctt[index]);
		}
		//获取IGMP资源锁
		mslock_api_init(&pigmp_ctt->mslock_ctt,"mslock_igmp_list",lockType_mutex);
		//创建IGMP发送线程
		msthread_api_create(&pigmp_ctt->msthread_ctt, "igmp_send", igmp_innerapi_done, pigmp_ctt);
	}
	return ms_true;
}
ms_bool igmp_api_deinit()
{
	IGMPContext *pigmp_ctt=&igmp_ctt;
	if(ms_true==pigmp_ctt->igmp_enable_manual){
		//停止IGMPf发送任务
		pigmp_ctt->flag_stop=ms_true;
		ms_u64 sys_base=mstime_api_sec();
		while(ms_true==pigmp_ctt->flag_stop){
			ms_debug("Wait for igmp_done stop");
			ms_msleep(100);
			if(ms_true==mstime_api_counterAsyncSec(&sys_base, ms_seconds(20))){
				break;
			}
		}
		msthread_api_destroy(&pigmp_ctt->msthread_ctt);

		//关闭所有原始套接字
		ms_s32 index=0;
		for( index=0;index<pigmp_ctt->net_ctt.num;index++){
			pigmp_ctt->net_ctt.pffurl_raw->url_close(&pigmp_ctt->net_ctt.rtpraw_urlctt[index]);
		}
		//释放锁
		mslock_api_deinit(&pigmp_ctt->mslock_ctt);
	}
	return ms_true;
}
#undef MSIGMP_C
